package com.gotobject.junit.internal.runners.model;

import com.gotobject.junit.If;
import static com.gotobject.junit.internal.runners.conditions.ConditionMaker.executed;
import static com.gotobject.junit.internal.runners.model.ConditionInvocation.of;
import org.junit.Ignore;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;

import java.lang.reflect.Constructor;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author Huascar A. Sanchez (huascar.sanchez@gmail.com)
 */
public class EachTestQualifiedNotifier extends EachTestNotifier implements QualifiedNotifier {
    private final boolean isAnnotatedWithIf;
    private       boolean isExecuted;
    private final Builder           builder;

    private EachTestQualifiedNotifier(Builder builder){
        super(builder.notifier, builder.desc);
        this.builder    = builder;
        isAnnotatedWithIf = this.builder.annotation != null;
        isExecuted = false;        
    }

    public boolean compileQualification(){
        final FrameworkMethod method = builder.method;
        return method.getAnnotation(Ignore.class) != null;
    }

    private void executeIfQualified(boolean runtime, boolean compiletime) {
        isExecuted = runtime || compiletime;
    }

    public void fireTestQualification(){
        if(isConditionalTest()){
            executeIfQualified(runtimeQualification(), compileQualification());
            if(!isExecuted()){                
                fireTestIgnored();
            }
        } else {
            qualifyExecution();
        }
    }

    public void fireQualifiedTestStarted(Statement statement) {
        try {
            fireTestStarted();
            statement.evaluate();
            fireTestStarted();
        } catch (AssumptionViolatedException e){
            addFailure(e);
        } catch (Throwable e) {
            addFailure(e);
        } finally {
            fireTestFinished();
        }
    }

    public boolean isConditionalTest() {
        return isAnnotatedWithIf;
    }

    /**
     * @return {@code true} if the qualification of a test indicates that
     *      we have not met the requirements to execute the test.
     */
    public boolean isIgnored(){
        return !isExecuted;
    }

    public boolean isExecuted() {
        return isExecuted;
    }

    private static Exception makeException(Throwable t) {
        try {
            final Constructor<Exception> c = Exception.class.getDeclaredConstructor(String.class, Throwable.class);
            return c.newInstance("Internal error when checking prerequisites.", t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private void qualifyExecution(){
        executeIfQualified(true, true);
    }

    public boolean runtimeQualification() {
		final AtomicBoolean result = new AtomicBoolean(false);
		try {
			result.set(
                    executed(
                            builder.desc, 
                            builder.notifier, 
                            of(
                                    builder.annotation, 
                                    builder.klass
                            )
                    )
            );
		} catch (Throwable t) {
            final Exception internalEx = makeException(t);
            addFailure(internalEx);            
		}

		return result.get();
	}

    /**
     * helper class to build a conditional tests.
     */
    public static class Builder {
        private         TestClass       klass;
        private         If              annotation;
        private final   Description     desc;
        private final   RunNotifier     notifier;
        private FrameworkMethod         method;

        public Builder(Description desc, RunNotifier notifier){
            this.desc = desc;
            this.notifier = notifier;
        }
        
        public EachTestQualifiedNotifier build(){
            return new EachTestQualifiedNotifier(this);
        }
        
        public Builder ifAnnotation(If annotation){
            this.annotation = annotation;
            return this;
        }
        
        public Builder testClass(TestClass klass){
            this.klass = klass;
            return this;
        }
        
        public Builder testMethod(FrameworkMethod method){
            this.method = method;
            return this;
        }
    }
}
